﻿//Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Threading;

namespace Microsoft.Ted.Wacel.Base
{
    public class Retrier
    {
        public static void Invoke(RetryPolicy policy, Action action)
        {
            for (int i = 0; i < policy.MaxRetries; i++)
            {
                try
                {
                    action();
                    return;
                }
                catch (Exception exp)
                {
                    bool safe = false;
                    foreach (var e in policy.Exceptions)
                    {
                        if (e == exp.GetType())
                        {
                            safe = true;
                            break;
                        }
                    }
                    if (!safe)
                        throw;
                    else
                        Thread.Sleep(policy.RetryPattern == RetryPattern.Progressive ? (i + 1) * policy.StaringInterval : policy.StaringInterval);
                }
            }
        }
        public static void Invoke<T1>(RetryPolicy policy, Action<T1> action, T1 t1)
        {
            for (int i = 0; i < policy.MaxRetries; i++)
            {
                try
                {
                    action(t1);
                    return;
                }
                catch (Exception exp)
                {
                    bool safe = false;
                    foreach (var e in policy.Exceptions)
                    {
                        if (e == exp.GetType())
                        {
                            safe = true;
                            break;
                        }
                    }
                    if (!safe)
                        throw;
                    else
                        Thread.Sleep(policy.RetryPattern == RetryPattern.Progressive ? (i + 1) * policy.StaringInterval : policy.StaringInterval);
                }
            }
        }
        public static void Invoke<T1,T2>(RetryPolicy policy, Action<T1,T2> action, T1 t1, T2 t2)
        {
            for (int i = 0; i < policy.MaxRetries; i++)
            {
                try
                {
                    action(t1, t2);
                    return;
                }
                catch (Exception exp)
                {
                    bool safe = false;
                    foreach(var e in policy.Exceptions)
                    {
                        if (e == exp.GetType())
                        {
                            safe = true;
                            break;
                        }
                    }
                    if (!safe)
                        throw;
                    else
                        Thread.Sleep(policy.RetryPattern == RetryPattern.Progressive ? (i+1) * policy.StaringInterval : policy.StaringInterval);
                }
            }
        }
        public static T2 Invoke<T1, T2>(RetryPolicy policy, Func<T1, T2> action, T1 t1)//TODO: How to write these overloads nicely?
        {
            Exception lastException = null;
            for (int i = 0; i < policy.MaxRetries; i++)
            {
                try
                {
                    return action(t1);
                }
                catch (Exception exp)
                {
                    lastException = exp;
                    bool safe = false;
                    foreach (var e in policy.Exceptions)
                    {
                        if (e == exp.GetType())
                        {
                            safe = true;
                            break;
                        }
                    }
                    if (!safe)
                        throw;
                    else
                        Thread.Sleep(policy.RetryPattern == RetryPattern.Progressive ? (i + 1) * policy.StaringInterval : policy.StaringInterval);
                }
            }
            if (lastException != null)
                throw lastException;
            else
                throw new InvalidOperationException();
        }
        public static T2 Invoke<T2>(RetryPolicy policy, Func<T2> action)//TODO: How to write these overloads nicely?
        {
            Exception lastException = null;
            for (int i = 0; i < policy.MaxRetries; i++)
            {
                try
                {
                    return action();
                }
                catch (Exception exp)
                {
                    lastException = exp;
                    bool safe = false;
                    foreach (var e in policy.Exceptions)
                    {
                        if (e == exp.GetType())
                        {
                            safe = true;
                            break;
                        }
                    }
                    if (!safe)
                        throw;
                    else
                        Thread.Sleep(policy.RetryPattern == RetryPattern.Progressive ? (i + 1) * policy.StaringInterval : policy.StaringInterval);
                }
            }
            if (lastException != null)
                throw lastException;
            else
                throw new InvalidOperationException();
        }
    }
    public abstract class RetryPolicy
    {
        public int StaringInterval { get; set; }
        public RetryPattern RetryPattern { get; set; }
        public abstract List<Type> Exceptions { get; set; }
        public int MaxRetries { get; set; }
        public RetryPolicy()
        {
            StaringInterval = 200;
            RetryPattern = RetryPattern.Fixed;
            MaxRetries = 3;
        }
    }
    public enum RetryPattern
    {
        Fixed = 0,
        Progressive = 1
    }
}
